fix(code): treat a missing plan.json at COMPLETE as a spurious completion - #186
Merged
Merged
Conversation
…tion
A PLAN run emitted the completion promise while plan-draft-writer was
still running in the BACKGROUND. The loop ended, the writer was abandoned
mid-flight, post-loop code review passed vacuously over an empty diff
("the base ref you passed equals HEAD, so nothing was examined"), and the
run exited 0 having produced no plan at all. Downstream, the user's
implementation-plan artifact looked done and was empty.
detect_spurious_complete could not see it. Its checks validate pendingTasks
INSIDE an existing plan.json, and its first branch returned "not spurious"
whenever the file was absent -- so "no plan at all", the case that actually
happens, was the one case nothing could catch.
A --prd run exists to produce plan.json, so claiming COMPLETE without one
is now flagged as PLAN_MISSING_AT_COMPLETION. Scoped to runs that were
asked for a plan; a run with no PRD never owed one and is untouched. The
AWAITING_USER hard stop still outranks the new branch, so a drafted plan
parked for review is not turned into a failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Generate Implementation Plan silently produces nothing. The run completes, exits 0, and the user is left with an implementation-plan artifact that looks done and is empty.
Reproduced 2026-08-10 (symphony-alpha PLN-1688). From the loop's own output, in order:
The orchestrator launched
plan-draft-writeras a background agent, said in plain text that it was waiting for it, and that same turn carried the completion promise. The loop ended, the writer was abandoned mid-flight, and the post-loop code review then passed vacuously over an empty diff — "No findings — but this is a vacuous pass... The base ref you passed equals HEAD, so nothing was examined" — and the process exited 0.Corroborating state left in the worktree:
state.jsonstill reads{"phase":"Phase 1: Planning","status":"IN_PROGRESS"}whileruns.logrecordsplan_execute→review_approve. Pre-exploration did finish (code-map.json17.6K,investigation-log.md16.4K,requirements-extract.json14.1K) — the run stopped exactly at the plan-writing step.detect_spurious_completehad the hole backwards. Its checks validatependingTasksinside an existingplan.json, and its first branch returned "not spurious" whenever the file was absent:So "no plan at all" — the case that actually happens — was the one case nothing could catch. A
--prdrun exists to produceplan.json; claiming COMPLETE without one is the strongest spurious-completion signal there is.Change
plan.jsonat COMPLETE is now flagged asPLAN_MISSING_AT_COMPLETION, routed through the existinghandle_spurious_completepath (telemetry, lock release, user-visible failure, exit 1) — so it bails before the post-loop code review rather than compounding misleading state, exactly as the sibling subcodes do.--prd/PRD_FILE). A run with no PRD never owed a plan and is untouched.AWAITING_USERhard stop still outranks it, so a plan drafted and parked at the Phase 1.1 review checkpoint is not turned into a failure.detect_spurious_completetakes the PRD path as an optional second argument defaulting to the global, so the existing call site is unchanged and tests can be explicit.plugins/codeversion bumped 1.14.7 → 1.14.8 per the Plugin Version Bump gate.Test plan
New
plugins/code/scripts/tests/test_spurious_complete.sh(6 cases).run-loop.shguardsmainwith[[ "${BASH_SOURCE[0]}" == "$0" ]], so the suite sources it and calls the function directly:plan.jsonon a--prdrun →PLAN_MISSING_AT_COMPLETION(the reported defect)plan.jsonand no PRD → not flagged (scope guard)AWAITING_USER+ no plan → not flagged (the documented hard stop still wins)PENDING_TASKS_AT_COMPLETIONPENDING_TASKS_BLOCKED_BY_QUESTIONSCounterfactual: neutering only the new branch fails exactly the first case and leaves the other five green — so the new code is doing the work, and the four pre-existing behaviours are provably unchanged.
Known gap: CI runs ruff/pyright/pytest and the TS suite; there is no bash-test job, so this suite (like the existing
plugins/code/hooks/tests/*.sh) is not gated. Run it withbash plugins/code/scripts/tests/test_spurious_complete.sh. Wiring the bash suites into CI is worth doing separately.Related
The other half of this failure is in
closedloop-ai/symphony-alpha(ISS-5872, URGENT): the desktop loop harness computesmissingRequiredviavalidateResultBundle, logsMissing required artifacts for PLAN: plan.json, then discards the result and finalizes the loop as COMPLETED witherror: null. Either layer alone would have caught this; both failed open. This PR closes the upstream one.